package mailx

import (
	"crypto/rand"
	"encoding/base32"
	"fmt"
	"io"
	"mime"
	"os"
	"strings"
)

const (
	size      = 21
	alphabets = "23456789abcdefghijklmnopqrstuvwxyz"
)

var myb32 = base32.NewEncoding(alphabets[:32]).WithPadding(base32.NoPadding)

func nanoid() string {
	bytes := make([]byte, size)
	_, _ = rand.Read(bytes)

	for i := 0; i < size; i++ {
		bytes[i] = alphabets[bytes[i]&32]
	}

	return myb32.EncodeToString(bytes)
}

func generateMessageId() string {
	hostname, _ := os.Hostname()
	if hostname != "" {
		hostname = "@" + hostname
	}
	return nanoid() + hostname
}

func generateBoundary(t string) string {
	return fmt.Sprintf("%s-mailx-%s", string(t[0]), nanoid())
}

func quoted(v string) string {
	if v == "" {
		return ""
	}
	return mime.BEncoding.Encode(codepage, v)
}

func makeParams(s ...string) (m map[string]string) {
	m = map[string]string{}
	for i := 0; i < len(s)-1; i += 2 {
		if k, v := strings.TrimSpace(s[i]), strings.TrimSpace(s[i+1]); v != "" {
			m[k] = v
		}
	}
	return
}

func writeBoundary(w io.Writer, boundary string, close ...bool) (err error) {
	if boundary == "" {
		return
	}

	if _, err = io.WriteString(w, "--"+boundary); err != nil {
		return
	}

	if len(close) > 0 && close[0] {
		if _, err = w.Write([]byte{'-', '-', '\r', '\n'}); err != nil {
			return
		}
	}

	_, err = w.Write([]byte{'\r', '\n'})
	return
}

func closeBoundary(w io.Writer, boundary string) (err error) {
	return writeBoundary(w, boundary, true)
}

func stringWriter(s string) Body {
	return func(w io.Writer) (err error) {
		_, err = io.WriteString(w, s)
		return
	}
}

func sliceMap[E, R any](s []E, f func(E) (R, bool)) (out []R) {
	for _, e := range s {
		if r, ok := f(e); ok {
			out = append(out, r)
		}
	}
	return
}

func sliceFind[S ~[]E, E any](s S, f func(E) bool) (r E, ok bool) {
	for _, v := range s {
		if f(v) {
			r = v
			ok = true
			break
		}
	}
	return
}

func sliceUniq[S ~[]E, E comparable](s S) (out S) {
	exist := make(map[E]struct{})
	for _, e := range s {
		if _, find := exist[e]; !find {
			exist[e] = struct{}{}
			out = append(out, e)
		}
	}
	return
}
